﻿#include "platform.h"
#include "logger.h"
#include "string_utils.h"
#include <cstdarg>
#include <chrono>

//
// 文件系统工具类
//
class FSUtils {
public:
#if defined(PLATFORM_WINDOWS)
    static const char c_path_separator = '\\';
#else
    static const char c_path_separator = '/';
#endif

    // 确定文件或目录是否存在。
    static bool isFileOrDirectoryExists(const std::string& path)
    {
#ifdef PLATFORM_WINDOWS
        return ::PathFileExistsA(path.c_str());
#else
        return ::access(path.c_str(), F_OK) == 0; // Test for existence
#endif
    }


    // 创建一个目录。
    static bool makeDirectory(const std::string& path)
    {
#ifdef PLATFORM_WINDOWS
        SECURITY_ATTRIBUTES attrib;
        attrib.bInheritHandle = FALSE;
        attrib.lpSecurityDescriptor = nullptr;
        attrib.nLength = sizeof(SECURITY_ATTRIBUTES);
        return ::CreateDirectoryA(path.c_str(), &attrib) == TRUE;
#else
        return ::mkdir(path.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) == 0;
#endif
    }

    // 创建递归的目录树。
    static bool makeDirectoryRecursive(const std::string& path)
    {
        auto pos = path.rfind(c_path_separator);
        if (pos != std::string::npos) {
            if (!makeDirectoryRecursive(path.substr(0, pos)))
                return false;
        }

        if (!isFileOrDirectoryExists(path)) {
            if (!makeDirectory(path))
                return false;
        }

        return true;
    }

    // 删除一个空目录。
    static bool removeDirectory(const std::string& path)
    {
#if defined(PLATFORM_WINDOWS)
        return ::RemoveDirectoryA(path.c_str()) == TRUE;
#else
        return ::rmdir(path.c_str()) == 0;
#endif
    }

    // 删除空的目录树。
    static bool removeDirectoryRecursive(const std::string& path)
    {
        auto pos = path.rfind(c_path_separator);
        if (pos != std::string::npos) {
            if (pos != path.length() - 1) { // path 的末尾不是路径分隔符
                if (!removeDirectory(path))
                    return false;
            }
            return removeDirectoryRecursive(path.substr(0, pos));
        }
        else {
            if (path.compare(".") == 0 || path.compare("..") == 0)
                return true;
    #if defined (PLATFORM_WINDOWS)
            if (path.rfind(':') != std::string::npos)   // 卷路径
                return true;
    #endif
            return removeDirectory(path);
        }
    }


    static void pathAppend(std::string& path, const std::string& more)
    {
        if (path[path.size() - 1] != c_path_separator) {
            if (more[0] != c_path_separator) {
                path += c_path_separator;
                path += more;
            }
            else {
                path += more;
            }
        }
        else {
            if (more[0] != c_path_separator)
                path += more;
            else
                path += more.substr(1, more.length() - 1);
        }
    }
};


//
// Windows 控制台类
//
#if defined(PLATFORM_WINDOWS)
class WinConsole {
private:
    HANDLE m_consoleHandle;
    bool m_allocated;
public:
    WinConsole() : m_consoleHandle(nullptr), m_allocated(false) {}

    ~WinConsole() { close(); }

    inline bool open() {
        if (m_consoleHandle == nullptr) {
            if (::AllocConsole())   // 为调用进程分配一个新的控制台
                m_allocated = true; // 分配成功

            // 从标准输出获取句柄
            m_consoleHandle = ::GetStdHandle(STD_OUTPUT_HANDLE);
        }

        if (m_consoleHandle != nullptr && m_allocated) {
            HWND hwnd = ::GetConsoleWindow();
            if (hwnd != nullptr) {
                HMENU hMenu = ::GetSystemMenu(hwnd, FALSE);
                if (hMenu != nullptr)
                    DeleteMenu(hMenu, SC_CLOSE, MF_BYCOMMAND);
            }
        }
        return m_consoleHandle != nullptr;
    }

    inline void close() {
        if (m_consoleHandle != nullptr) {
            if (m_allocated)
                ::FreeConsole();
            m_consoleHandle = nullptr;
        }
    }

    inline bool isValid() { return m_consoleHandle != nullptr; }

    inline void setTextAttribute(WORD wAttributes) {
        ::SetConsoleTextAttribute(m_consoleHandle, wAttributes);
    }

    inline void write(const void* buf, DWORD bytesToWrite) {
        DWORD bytesWritten;
        ::WriteConsoleA(m_consoleHandle, buf, bytesToWrite, &bytesWritten, nullptr);
    }
};

static WinConsole g_wincon;

#endif


//
// ConsoleAppender
//
ConsoleAppender::ConsoleAppender()
{
}


ConsoleAppender::~ConsoleAppender()
{
#if defined(PLATFORM_WINDOWS)
    g_wincon.close();
#endif
}


bool ConsoleAppender::start()
{
#if defined(PLATFORM_WINDOWS)
    return g_wincon.open();
#endif
    return true;
}


void ConsoleAppender::append(int level, const std::string& msg)
{
#if defined(PLATFORM_WINDOWS)
    if (g_wincon.isValid()) {
        switch (level) {
        case LL_DEBUG: g_wincon.setTextAttribute(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); break; // White
        case LL_ERROR: g_wincon.setTextAttribute(FOREGROUND_RED); break; // Red
        default: g_wincon.setTextAttribute(FOREGROUND_INTENSITY); break; // Ligth Gray
        }

        g_wincon.write(msg.data(), (DWORD)msg.size());
        return;
    }
#endif

    switch (level) {
    case LL_DEBUG: printf("\033[97m%s\033[0m", msg.c_str()); break; // White
    case LL_ERROR: printf("\033[31m%s\033[0m", msg.c_str()); break; // Red
    default: printf("\033[37m%s\033[0m", msg.c_str()); break; // Light Gray
    }
}


//
// FileAppender
//

FileAppender::FileAppender(const char* logsDir, const char* fileNamePrefix)
    : m_logsDir(logsDir)
    , m_fileNamePrefix(fileNamePrefix)
    , m_stream(nullptr)
    , m_writable(false)
{
}


FileAppender::~FileAppender()
{
    if (m_writable) {
        /* 等待队列中的日志全部写入磁盘。最多等10秒*/
        auto t0 = std::chrono::steady_clock::now();
        while(!m_msgQue.empty()) {
            auto interval = std::chrono::steady_clock::now() - t0;
            auto seconds_elapsed = std::chrono::duration_cast<std::chrono::seconds>(interval).count();
            if (seconds_elapsed >= 10)
                break;
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        }
    }

    m_writeThread.interrupt();
    m_writeThread.join();

    closeCurrentFile();
}


bool FileAppender::start()
{
    if (!FSUtils::isFileOrDirectoryExists(m_logsDir)) {
        if (!FSUtils::makeDirectoryRecursive(m_logsDir)) {
            return false;   // 路径不正确
        }
    }

    if (!m_writeThread.start(&FileAppender::writeThreadFunc, this))
        return false;

    m_writable = true;
    return true;
}


void FileAppender::append(int level, const std::string& msg)
{
    (void)level;    // unused

    if (m_writable) {
        m_queMutex.lock();
        m_msgQue.push(msg);
        m_queMutex.unlock();
    }
}


void FileAppender::writeThreadFunc()
{
    std::string filePath;
    std::string fileNameWithExt;
    std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now();

    while (!m_writeThread.isInterrupted()) {
        if (m_msgQue.empty()) {
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
            continue;
        }

        auto interval = std::chrono::steady_clock::now() - t0;
        auto secondsElapsed = std::chrono::duration_cast<std::chrono::seconds>(interval).count();

        if (m_currentFilePath.empty() || secondsElapsed > 60) {
            /* 获取当前系统时间 */
            auto raw_time = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
            auto now = std::localtime(&raw_time);

            /* 使用当前日期设置新的日志文件名 */
            stringAppendF(fileNameWithExt, "%s%04d%02d%02d.txt",
                          m_fileNamePrefix.c_str(), now->tm_year + 1900, now->tm_mon + 1, now->tm_mday);

            filePath = m_logsDir;
            FSUtils::pathAppend(filePath, fileNameWithExt);

            closeCurrentFile();
            openFile(filePath);

            t0 = std::chrono::steady_clock::now();
        }

        if (m_stream != nullptr) {
            m_queMutex.lock();
            auto& msg = m_msgQue.front();
            fwrite(msg.c_str(), 1, msg.size(), m_stream);
            fflush(m_stream);
            m_msgQue.pop();
            m_queMutex.unlock();
        }
    }
}


bool FileAppender::openFile(const std::string& filePath)
{
    if (m_currentFilePath.compare(filePath) != 0) { // 文件名改变
        closeCurrentFile();
        m_currentFilePath = filePath;
    }

    if (m_stream == nullptr) {
        m_stream = fopen(m_currentFilePath.c_str(), "a+");
        if (m_stream == nullptr)
            return false;
    }

    return true;
}


void FileAppender::closeCurrentFile()
{
    if (m_stream != nullptr) {
        fclose(m_stream);
        m_stream = nullptr;
    }
}


//
// Logger
//

Logger Logger::_instance;

static inline const char* getLevelName(int level)
{
    switch (level) {
    case LL_DEBUG: return "DEBUG";
    case LL_INFO: return "INFO ";
    case LL_ERROR: return "ERROR";
    default: return "";
    }
}


Logger::Logger()
    : m_consoleAppender(nullptr)
    , m_fileAppender(nullptr)
    , m_currentLevel(LL_ERROR)
{
}

Logger::~Logger()
{
    if (m_consoleAppender)
        delete m_consoleAppender;
    if (m_fileAppender)
        delete m_fileAppender;
}


void Logger::setCurrentLevel(int level)
{
    m_currentLevel = level;
}


bool Logger::isLoggable(int level)
{
    if (level >= m_currentLevel) {
        if (m_consoleAppender || m_fileAppender)
            return true;
    }

    return false;
}


bool Logger::enableFileLog(const char* logsDir, const char* fileNamePrefix)
{
    std::lock_guard<std::mutex> locker(m_mutex);

    FileAppender* appender = new(std::nothrow) FileAppender(logsDir, fileNamePrefix);
    if (!appender)
        return false;

    if (!appender->start()) {
        delete appender;
        return false;
    }

    if (m_fileAppender)
        delete m_fileAppender;

    m_fileAppender = appender;

    return true;
}


void Logger::disableFileLog()
{
    std::lock_guard<std::mutex> locker(m_mutex);

    if (m_fileAppender) {
        delete m_fileAppender;
        m_fileAppender = nullptr;
    }
}


bool Logger::enableConsoleLog()
{
    if (m_consoleAppender)
        return true;

    std::lock_guard<std::mutex> locker(m_mutex);

    ConsoleAppender* appender = new (std::nothrow) ConsoleAppender();
    if (!appender)
        return false;

    if (!appender->start()) {
        delete appender;
        return false;
    }

    m_consoleAppender = appender;

    return true;
}


void Logger::disableConsoleLog()
{
    std::lock_guard<std::mutex> locker(m_mutex);

    if (m_consoleAppender) {
        delete m_consoleAppender;
        m_consoleAppender = nullptr;
    }
}


void Logger::writeln(int level, const char* fmt, ...)
{
    std::lock_guard<std::mutex> locker(m_mutex);

    auto tp = std::chrono::system_clock::now();
    auto rawtime = std::chrono::system_clock::to_time_t(tp);
    auto timeinfo = std::localtime(&rawtime);
    auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(tp.time_since_epoch());

    std::string msg;
    stringAppendF(msg, "[%04d-%02d-%02d %02d:%02d:%02d.%03d] ",
                  timeinfo->tm_year + 1900, timeinfo->tm_mon + 1, timeinfo->tm_mday,
                  timeinfo->tm_hour, timeinfo->tm_min, timeinfo->tm_sec, ms.count() % 1000);
#if defined(PLATFORM_WINDOWS)
    stringAppendF(msg, "[thread:%u] ", ::GetCurrentThreadId());
#else
    if (sizeof(pthread_t) == 8)
        stringAppendF(msg, "[thread:%llu] ", (uint64_t)::pthread_self());
    else
        stringAppendF(msg, "[thread:%u] ", (uint32_t)::pthread_self());
#endif
    stringAppendF(msg, "[%s] ", getLevelName(level));

    va_list args;
    va_start(args, fmt);
    stringAppendV(msg, fmt, args);
    va_end(args);

    msg.push_back('\n');

    if (m_consoleAppender)
        m_consoleAppender->append(level, msg);

    if (m_fileAppender)
        m_fileAppender->append(level, msg);
}
